contents
1. 클린 아키텍처란?
- 클린 아키텍처는 소프트웨어 시스템을 유연하고, 변경에 강하고, 유지보수와 테스트가 편리하며, 독립성 있는 구조로 설계하기 위한 소프트웨어 아키텍트 패턴입니다.
- 2012년 Robert C. Martin(“Uncle Bob”)에 의해 제안되었으며, 비즈니스/도메인 로직(핵심 규칙)과 외부 구현(프레임워크, UI, DB 등)을 명확히 분리하는 데 중점을 둡니다.
2. 핵심 설계 원칙
1. 관심사 분리(Separation of Concerns)
- 각 구성 요소(레이어)가 담당 책임과 역할이 명확하게 구분되어야 하며, 변경 시 다른 영역에 영향이 최소화됩니다.
- 변화에 따른 유지보수 및 테스트가 쉬워지고, 시스템 확장성이 향상됩니다.
2. 의존성 규칙(Dependency Rule)
- 의존성은 항상 안쪽(코어/도메인)으로만 향합니다.
- 내부 레이어(비즈니스, 엔티티)는 외부 구현에 의존하지 않으며, 외부는 추상화/인터페이스에만 의존해야 합니다.
- 고수준 모듈과 저수준 모듈이 추상화에 의존해 결합도를 낮춥니다.
3. SOLID 원칙
- SRP(단일 책임 원칙): 하나의 컴포넌트, 클래스, 모듈은 오직 한 가지 책임만 가져야 함.
- OCP(개방-폐쇄 원칙): 확장에는 열려 있고, 변경에는 닫혀 있어야 함.
- LSP(리스코프 치환 원칙): 상위 타입을 하위 타입으로 치환해도 동작에 문제가 없어야 함.
- ISP(인터페이스 분리 원칙): 작은 단위의 인터페이스 설계 권장.
- DIP(의존관계 역전 원칙): 상세 구현(외부)에 의존하지 않고 추상에 의존해야 함.
- 클린 아키텍처의 기반 설계 철학.
3. 클린 아키텍처 레이어 구조
클린 아키텍처는 4개의 주요 계층으로 구성됩니다. 안쪽에서 바깥쪽으로 갈수록 외부 기술 의존도와 구현 세부사항이 높아집니다.
A. 엔티티(Entities)
- 시스템의 핵심 도메인/비즈니스 규칙을 담고 있는 클래스나 오브젝트.
- 데이터베이스, UI, 외부 기술 등에 독립적.
- 가장 변경이 적고 안정적인 계층.
B. 유스케이스(Use Cases, 인터랙터)
- 애플리케이션별 비즈니스 규칙.
- 엔티티를 사용해 사용자 또는 시스템의 특정 행동/처리 로직 구현.
- 애플리케이션 비즈니스 룰에 집중, 데이터 흐름 및 행위 정의.
C. 인터페이스 어댑터(Interface Adapters)
- 내부와 외부를 연결, 데이터 포맷/구조 변환을 담당.
- 예시: 컨트롤러(MVC), 프레젠터, DTO, 게이트웨이, 데이터 매퍼 등.
D. 프레임워크와 드라이버(Frameworks & Drivers)
- DB, UI, 웹 프레임워크, 외부 시스템 등 실제 구현부.
- 가능한 한 도메인/비즈니스 로직과 분리해 추상화 및 의존성 역전 적용.
레이어 정리
[외부 프레임워크] <--- [인터페이스 어댑터] <--- [유스케이스] <--- [엔티티]
(가장 바깥) (중간 연결) (비즈니스 룰) (핵심 도메인)
- 안쪽 계층으로 갈수록 변경이 적고, 바깥은 빈번한 기술 변화에 쉽게 대응 가능.
- 안쪽(엔티티, 유스케이스)은 바깥쪽(프레임워크, DB, UI 등)과 독립적 설계.
4. Clean Architecture의 주요 특징
- 도메인 중심: 비즈니스/도메인 규칙이 시스템의 중심, 최상위 계층으로 분리.
- 프레임워크 독립: 어떤 웹 프레임워크/외부 라이브러리에도 의존하지 않음.
- UI/DB 독립: 웹 UI, 콘솔 UI 등 교체가 용이하며 DB 교체도 쉬움.
- 테스트 용이성: 각 계층별 독립성과 추상화로 인해 모듈별 단위 테스트가 수월.
- 변경/확장에 강함: 외부 변화가 핵심 비즈니스 로직에 영향 최소화.
5. 클린 아키텍처 설계 예시
- 요청이 들어오면(Controller): 인터페이스 어댑터에서 입력을 받아 DTO/객체로 변환.
- 유스케이스(Interactor)가 실행: 엔티티/비즈니스 규칙 객체를 활용해 처리 및 결과 생성.
- 결과가 인터페이스 어댑터로 이동: 결과를 프레젠터, 뷰모델 등 필요한 형식으로 변환.
- 외부(DB, UI 등)에 전달: 데이터 저장, 화면 출력 등 내부와 외부의 경계를 유지.
6. Clean Architecture 베스트 프랙티스
- 의존성은 추상에 맞추고, 구현체는 쉽게 교체하도록 설계.
- 인터페이스와 구현을 분리, DIP 적용.
- 각 계층의 책임과 경계를 명확하게 설정.
- 영속성(DB), 프레젠테이션(UI) 등 외부와 도메인 로직을 철저히 분리.
7. 요약
- 클린 아키텍처는 견고한 소프트웨어 시스템 구축의 표준 설계 지침이자 패턴입니다.
- 유지보수성, 유연성, 확장성이 뛰어난 구조를 제공하며, 외부 변화와 기술 교체에 강해서 장기적인 프로젝트에 유리합니다.
스프링에서 간단한 클린아키텍처 예제를 만들어 보겠습니다.
프로젝트 구조 예시
src/main/java/com/example/cleanarchitecture/
├── domain/
│ ├── model/
│ │ └── Product.java // 엔티티
│ └── repository/
│ └── ProductRepository.java // 도메인 저장소 인터페이스
├── application/
│ └── service/
│ └── ProductService.java // 비즈니스 로직(유스케이스)
├── infrastructure/
│ ├── persistence/
│ │ └── ProductRepositoryImpl.java // JPA 저장소 구현
│ └── web/
│ └── ProductController.java // REST 컨트롤러
├── CleanArchitectureApplication.java // Spring Boot 메인 클래스
1. 도메인 계층 – 엔티티 & 저장소
// domain/model/Product.java
public class Product {
private Long id;
private String name;
private double price;
// 생성자, Getter, Setter 등
}
// domain/repository/ProductRepository.java
import java.util.List;
import java.util.Optional;
public interface ProductRepository {
Optional<Product> findById(Long id);
List<Product> findAll();
Product save(Product product);
}
2. 애플리케이션 계층 – 유스케이스 / 비즈니스 로직
// application/service/ProductService.java
import com.example.cleanarchitecture.domain.model.Product;
import com.example.cleanarchitecture.domain.repository.ProductRepository;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public Optional<Product> getProductById(Long id) {
return productRepository.findById(id);
}
public List<Product> getAllProducts() {
return productRepository.findAll();
}
public Product addProduct(Product product) {
return productRepository.save(product);
}
}
3. 인프라 계층 – DB 구현
// infrastructure/persistence/ProductRepositoryImpl.java
import com.example.cleanarchitecture.domain.model.Product;
import com.example.cleanarchitecture.domain.repository.ProductRepository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductRepositoryImpl extends JpaRepository<Product, Long>, ProductRepository {
// JpaRepository가 기본 CRUD 제공
}
4. 웹 계층 – REST 컨트롤러
// infrastructure/web/ProductController.java
import com.example.cleanarchitecture.application.service.ProductService;
import com.example.cleanarchitecture.domain.model.Product;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public List<Product> getAllProducts() {
return productService.getAllProducts();
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProductById(@PathVariable Long id) {
Optional<Product> product = productService.getProductById(id);
return product.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
Product savedProduct = productService.addProduct(product);
return ResponseEntity.ok(savedProduct);
}
}
5. Spring Boot 메인 클래스
// CleanArchitectureApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CleanArchitectureApplication {
public static void main(String[] args) {
SpringApplication.run(CleanArchitectureApplication.class, args);
}
}
요약:
각 계층은 자신만의 책임을 가지며 필요한 추상화 및 내부 레이어만 의존합니다.
- 도메인: 핵심 비즈니스 로직과 모델 정의.
- 애플리케이션: 유스케이스/비즈니스 로직 담당.
- 인프라: DB 접근 등 기술적 구현 담당.
- 웹: REST API로 앱 기능 제공.
이러한 계층적 분리는 클린 아키텍처의 핵심 원칙을 실제로 구현한 예제입니다.
솔직히 위의 코드를 봐선 일반적으로 구현하는 API를 리턴하는 MVC와 다른 점이 잘 눈에 보이지 않습니다. 이에 대한 설명을 조금 더 해보겠습니다.
차이점 요약: Clean Architecture vs. 일반 Spring MVC
1. 의존성의 방향
- Spring MVC는 Controller → Service → Repository (그리고 Model/Entity)의 구조이며, 대부분의 코드가 Spring Framework의 클래스 및 어노테이션에 의존적인 구조입니다. 즉, 비즈니스 로직도 Spring의 구조와 강하게 결합되어 있습니다.
- 클린 아키텍처는 레이어간 의존성이 "안쪽(도메인, 유스케이스)으로만" 향하고, 가장 바깥(프레임워크, 인프라)은 내부 핵심 로직(도메인/유스케이스)을 전혀 모릅니다. 즉, 도메인 모델과 유스케이스(비즈니스 로직)는 프레임워크에 독립적이며, 그 반대는 불가능합니다.
- → 예시: 서비스/유스케이스가
@Repository,@Entity등 Spring 어노테이션을 직접 쓰지 않음.
- → 예시: 서비스/유스케이스가
2. 레이어의 책임 범위와 변경에 대한 내구성
- Spring MVC:
- Controller, Service, Repository가 Spring에 강하게 의존하고, 변경이 있으면 전체 계층에 영향이 감.
- 단위 테스트 시에도 스프링 컨텍스트와 Repository/JPA를 많이 필요로 함.
- 클린 아키텍처:
- 도메인 로직/유스케이스는 테스트 시 프레임워크 없이 순수 Java 객체로 동작 가능.
- 인프라/프레임워크의 변화(DB 교체, API/컨트롤러 교체 등)가 비즈니스로직에 영향 거의 없음.
- 각 레이어는 인터페이스/추상화로 연결되며, 실제 구현체는 외부에서 주입.
3. Use Case (유스케이스)와 Application 서비스의 강조
- Spring MVC 패턴에서는 Service가 비즈니스 로직, 데이터 접근, 트랜잭션 등을 모두 처리할 수 있음.
- 클린 아키텍처에서는 “유스케이스(Interactor)”라는 별도의 계층이 존재. 이 부분이 도메인 모델을 이용하여 요구되는 정보를 추출하거나, 데이터를 조작, 비즈니스 규칙 실행.
- 유스케이스는 인프라와 프레임워크(예: JPA, REST, Spring 등) 기술을 직접 사용하지 않고, 필요한 API만 인터페이스로 정의.
4. 프레임워크/외부 기술로부터의 독립성
- Spring MVC는 다수의 Bean, Annotation, JPA 등의 상세구현에 강하게 묶임.
- Clean Architecture는 실제 비즈니스 로직(도메인, 유스케이스)이 Spring 또는 JPA 등 외부기술 없이 동작하는 형태로 분리되어, 가장 안쪽 계층은 프레임워크와 완전 분리됨.
핵심 구조 예시
[Spring Controller, JPA Repository, DTO, DB] (외부 Infra/Framework Layer)
↓ (인터페이스, 추상화로 연결)
[UseCase (Application Layer)] ← (비즈니스 로직과 트랜잭션)
↓
[Domain Model (Entity Layer)] ← (핵심 규칙/도메인 객체)
- 의존성은 오직 내부(도메인/유스케이스)로만 향하고, 바깥쪽(프레임워크, 인프라)는 인터페이스/추상화만 보고 결합됨.
결론
- 클린 아키텍처는 복잡한 변화/스케일/오랜 유지관리가 필요한 프로젝트에서 비즈니스 로직의 재사용성과 테스트 용이성, 프레임워크 독립성을 제공합니다.
- 단순 Spring MVC는 빠른 개발에는 좋지만, 시스템 복잡성이 올라가면 의존성 결합과 유지보수에 취약해집니다.
- 따라서, “클린 아키텍처”는 ‘도메인과 비즈니스 규칙’을 진짜 중심에 놓고, 나머지(컨트롤러, DB, 프레임워크)는 쉽게 교체/변경 가능한 “세부사항”으로 취급합니다.
핵심 요약:
Spring MVC는 프레임워크 중심 구조, Clean Architecture는 비즈니스 규칙 중심의 의존성 역전과 프레임워크 분리 구조입니다.
이로 인해 테스트/재사용/유지보수/확장성 모두 Clean Architecture가 복잡한 대형, 장기 서비스에 더 유리합니다.
references